#!/usr/bin/env python3

"""
ODR-Bridge-EPG

Feed in a multiplex configuration file and an output bitstream path.

The mux config will be scanned for services linked to an EPG packet data channel. The service SI files will
be resolved and demarshalled.

These will then be combined to a multiplex SI file. This is then used to pull back logo files for each service in the 4 
lowest sizes. Also the specified number of days of PI files. All these are then encoded to an MOT directory bitstream.

"""

import argparse
import os, sys
from datetime import datetime, timedelta
import urllib.request, urllib.error
import logging
from json import dumps, JSONEncoder
import random
import string
import re
import csv
from pprint import pprint

from PIL import Image
from io import BytesIO

from spi import DabBearer, IpBearer, Time, ServiceInfo, Multimedia, ShortName, MediumName, xml, binary, ShortDescription, LongDescription

from mot import MotObject, ContentType, SortedHeaderInformation
from mot.epg import EpgContentType, ScopeId, ScopeStart, ScopeEnd

from msc.datagroups import encode_directorymode
from msc.packets import encode_packets


def filter_by_bearer(service):
    bearers = [x for x in service.bearers if isinstance(x, (DabBearer))] # can include IP bearers if you like
    return service

def get_services_for_bearers(bearers, url, clientkey):
    logger.debug('getting services for bearers from %s : %s', url, bearers)
    req = urllib.request.Request(url)
    if clientkey:
        logger.debug("Client ID passed to %s", url)
        req.add_header("Authorization", "ClientIdentifier " + clientkey)
    try:
        response = urllib.request.urlopen(req)
        logger.debug('SI successfully opened')
        unmarshalled = xml.unmarshall(response.read().decode('utf-8'))
        logger.debug('SI successfuly unmarshalled')
    except urllib.error.HTTPError as err:
        if err.code == 404:
            logger.debug('could not find SI file at: %s', url)
        elif err.code == 401 or err.code == 500:
           logger.exception('authorization failure accessing SI file at: %s', url)
        else:
            logger.exception('error accessing SI file at: %s', url)

    services = list(filter(lambda s : len(list(filter(lambda b : b in bearers, s.bearers))) > 0, unmarshalled.services))
    logger.debug('number of services found %d', len(services))
    if len(services) == 0:
        logger.debug('no services found for bearers %s from url %s',bearers, url)
    return services

def get_filename():
    return ''.join(random.choice(string.ascii_lowercase) for i in range(6))

def encode_image(url):
    logger.debug('encoding image from %s', url)
    req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
    data = urllib.request.urlopen(req).read()
    logger.debug('read %d bytes of image data', len(data))
    if len(data) > 5000:
        logger.debug('WARN: image size is excessive (greater than 5kBytes)')
        image = Image.open(BytesIO(data))
        image = image.convert("P", palette=Image.ADAPTIVE)
        image.save("compressed.png", optimize=True)
        temp = open('compressed.png','rb')
        data = temp.read()
        temp.close()
        os.remove('compressed.png')
        logger.debug('re-sized to %d bytes of image data', len(data))
    
    return data

parser = argparse.ArgumentParser(description='Encodes an SPI bitstream for services in a multiplex configuration file')
parser.add_argument('f',  nargs=1, help='multiplex configuration file')
parser.add_argument('-o', dest='output', help='output bitstream file')
parser.add_argument('-X', dest='debug', action='store_true', help='turn debug on')
parser.add_argument('-d', dest='days', type=int, default=2, help='number of days ahead to encode schedule files')
parser.add_argument('-p', dest='packet_size', type=int, default=96, help='Packet size in bytes')
parser.add_argument('-a', dest='packet_address', type=int, default=1, help='Packet address')
parser.add_argument('-D', dest='datagroup_output', action='store_true', help='output Datagroups instead of Packets')
parser.add_argument('-c', dest='clientids', help='filename of CSV file of fqdn,ClientID key values')
args = parser.parse_args()

if args.debug:
    logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('odr.bridge.epg')
logging.getLogger('spi.binary').setLevel(logging.DEBUG)
logging.getLogger('spi.xml').setLevel(logging.DEBUG)

# read in the mux config
c = open(args.f[0]).read()

# read in any fqdn / client IDs
clientids = list()

if args.clientids:
    with open(args.clientids, newline='') as csvfile:
        clientids = list(filter(None, csv.reader(csvfile)))
        
def calculate_scope(schedule): # this should probably be in the main codebase
    start = schedule.scope.start
    end = schedule.scope.end
    if start is not None and end is not None: return (start, end)
    if start is None:
        for programme in schedule.programmes:
            for location in programme.locations:
                for time in location.times:
                    if not isinstance(time, Time): continue
                    if start is None or time.actual_time < start: start = time.actual_time
                    if end is None or (time.actual_time + time.actual_duration) > end: end = (time.actual_time + time.actual_duration)
    return (start, end)

class Generator:

    def __init__(self, days, output, packet_size, packet_address, datagroup_output, ecc, eid, label, shortlabel):
        self.days = days if days else 0
        self.output = output if output else 'output.dat'
        self.packet_size = packet_size
        self.packet_address = packet_address
        self.datagroup_output = datagroup_output
        self.ecc = ecc
        self.eid = eid
        self.label = label
        self.shortlabel = shortlabel


        if self.datagroup_output:
            logger.debug('creating bitstream generator: days=%d, using datagroups', days)       
        else:
            logger.debug('creating bitstream generator: days=%d, using packets: packet_size=%d, packet_address=%d', days, packet_size, packet_address)

    def generate_epg(self, responses):

        # lets provision a list of the files and encoders that we'll use to assemble the directory 
        # list of tuples: (filename, params, function, args, kwargs)
        objects = []

        # our list of services
        all_services = []

        logger.debug('responses: %s', responses)
        
        now = datetime.today()

        for response in responses:
            fqdn = response['fqdn']
            bearers = response['bearers']
            servers = response['servers']
            app = response['app']
            clientkey = ""
            
            # is there a client id for this fqdn
            for clientid in clientids:
                if (clientid[0] + ".") == fqdn:
                    clientkey = clientid[1]

            # sort servers in priority (lowest), weighting highest
            servers = sorted(servers, key=lambda x: (-x['priority'], x['weight']))

            # acquire an SI file, starting with the lower priority
            for server in servers:
                domain = server['target'][:-1]
                if app == "radiospi":
                    url = 'https://%s/radiodns/spi/3.1/SI.xml' % domain 
                else:
                    url = 'http://%s/radiodns/spi/3.1/SI.xml' % domain 
                logger.debug('getting SI document from %s', url)
                try:
                    services = get_services_for_bearers(bearers, url, clientkey)
                except:
                    continue # move onto the next SRV record
                all_services.extend(services)

                # encode service logos and prepare the SI file
                logger.debug('encoding service logos')
                for service in services:

                    logger.debug('==== Now working on Station %s =====', service)

                    service.genres = list(filter(None, service.genres))

                    if len(service.genres) == 0:
                        logger.debug('No genres are correctly defined for this station')

                    logger.debug('encoding service logo for: %s', service)

                    new_media = [] 
                    logo_count = 1
                    for media in filter(lambda m: 
                        m.type in (Multimedia.LOGO_COLOUR_SQUARE, Multimedia.LOGO_COLOUR_RECTANGLE) or
                        (m.content in ('image/png') and (m.width, m.height) in ((32, 32), (112, 32), (128, 128), (320, 240))), 
                        service.media):

                        if media.type == Multimedia.LOGO_COLOUR_SQUARE :
                             media.width = 32
                             media.height = 32
                        elif media.type == Multimedia.LOGO_COLOUR_RECTANGLE :
                             media.width = 112
                             media.height = 32

                        logger.debug('Width %d and height %d', media.width, media.height)
   
                        # create a sensible contentname
                        logger.debug('Media content type is %s', media.content)
                        name = "{service}_{count}_{width}_{height}.png".format(
                            service=service.get_name(ShortName.max_length).text.replace(" ", "_"),
			                   count=logo_count,
                            width=media.width,
                            height=media.height)
                        logo_count+=1
                        logger.debug('creating MOT image %s from url: %s', name, media.url)
                        
                        # create MOT Object
                        o = MotObject(name, encode_image(media.url), ContentType.IMAGE_PNG)
                        objects.append(o)

                        # set the name
                        media.url = name

                        # set the type
                        if (media.width, media.height) == (32, 32):
                            media.type = Multimedia.LOGO_COLOUR_SQUARE
                        elif (media.width, media.height) == (112, 32):
                            media.type = Multimedia.LOGO_COLOUR_RECTANGLE
                        else:
                            media.type = Multimedia.LOGO_UNRESTRICTED 

                        new_media.append(media)
  
                    service.media = new_media
     
                    # get the relevant bearer for this mux

                    bearer = list(filter(lambda x: isinstance(x, DabBearer) and x.ecc == ecc and x.eid == eid, service.bearers))[0]

                    # now find PI files
                    if self.days > 0 :
                        for i in range(0, self.days):
                            # pi_url = 'http://%s/radiodns/spi/3.1/dab/%03x/%04x/%04x/%01x/%s' % (domain, ((bearer.sid >> 4 & 0xf00) + bearer.ecc), bearer.eid, bearer.sid, bearer.scids, day.strftime("%Y%m%d_PI.xml"))
                            # try:
                            #     day = now + timedelta(days=i)
                            #     filename = '%s_%02x.%04x.%04x.%01x_PI.xml' % (day.strftime("%Y%m%d"), bearer.ecc, bearer.eid, bearer.sid, bearer.scids)
                            #     # Uncomment this next line if you want to use legacy filenames
                            #     # filename = 'w%sd%04xc0.EHA' % (day.strftime("%Y%m%d"), bearer.sid)
                            #     logger.debug('making request for PI file to: %s to write out as: %s', pi_url, filename)
                            #     f = urllib.request.urlopen(pi_url)
                            #     data = f.read().decode("utf-8")
                            #     logger.debug('read %d bytes', len(data))
                            # 
                            # except urllib.error.HTTPError as err:
                            #     if err.code == 404:
                            #         logger.debug('could not find PI file at: %s', pi_url)
                            #     else:
                            #         logger.exception('error finding PI file at: %s', pi_url)
                            #     
                            day = now + timedelta(days=i)
                            filename = '%s_%02x.%04x.%04x.%01x_PI.xml' % (day.strftime("%Y%m%d"), bearer.ecc, bearer.eid, bearer.sid, bearer.scids)
                            date1 = day.strftime("%Y-%m-%d")
                            date2 = (day + timedelta(days=i+1)).strftime("%Y-%m-%d")
                            data = f"""<?xml version="1.0" encoding="UTF-8"?>
<epg xmlns="http://www.worlddab.org/schemas/spi/31"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xml:lang="en"
     xsi:schemaLocation="http://www.worlddab.org/schemas/spi/31 spi_31.xsd">
    <schedule creationTime="{date1}T18:16:53" originator="Global Radio">
        <scope startTime="{date1}T22:00:00+01:00" stopTime="2020-06-10T01:00:00+01:00" />
            <programme id="crid://epg.musicradio.com/programmes/2817/1706858" shortId="1706858">
                <shortName>Marvin</shortName>
                <mediumName>Marvin Humes</mediumName>
                <longName>The Capital Late Show With Marvin Humes</longName>
                <location>
                    <time actualDuration="PT3H0M" actualTime="{date1}T22:00:00+01:00" duration="PT3H0M" time="{date1}T22:00:00+01:00"/>
                </location>
                <mediaDescription>
                    <longDescription>Marvin Humes has your late-night soundtrack sorted with the best new music around | @capitalofficial</longDescription>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="320" url="http://mediaweb.musicradio.com/ArtWork/SES/e0366f1b-0f34-4b35-a70d-db10567199ca" width="600"/>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="2048" url="http://mediaweb.musicradio.com/ArtWork/SES/99221a8c-7eed-446e-9f26-eae75fbd41f5" width="2048"/>
                </mediaDescription>
                <memberOf id="crid://epg.musicradio.com/programmes/2817" shortId="2817"/>
                <link uri="http://www.capitalfm.com/radio/shows-presenters/marvin-humes"/>
            </programme>
            <programme id="crid://epg.musicradio.com/programmes/2065/1706859" shortId="1706859">
                <shortName>Will C</shortName>
                <mediumName>Will Cozens</mediumName>
                <longName>Will Cozens</longName>
                <location>
                    <time actualDuration="PT3H0M" actualTime="{date2}T01:00:00+01:00" duration="PT3H0M" time="{date2}T01:00:00+01:00"/>
                </location>
                <mediaDescription>
                    <longDescription>Let&#39;s do this. Will Cozens is on hand with massive tunes all through the AM | @capitalofficial</longDescription>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="320" url="http://mediaweb.musicradio.com/ArtWork/SES/ae7dd64a-3da6-4e6d-b544-f40c3e2c7206" width="600"/>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="2048" url="http://mediaweb.musicradio.com/ArtWork/SES/4ebe17e9-8c96-4ac9-bbef-794a544b77e9" width="2048"/>
                </mediaDescription>
                <memberOf id="crid://epg.musicradio.com/programmes/2065" shortId="2065"/>
                <link uri="http://www.capitalfm.com/london/radio/shows-presenters/will-cozens"/>
            </programme>
            <programme id="crid://epg.musicradio.com/programmes/7838/1706860" shortId="1706860">
                <shortName>Lauren</shortName>
                <mediumName>Lauren Layfield</mediumName>
                <longName>Lauren Layfield</longName>
                <location>
                    <time actualDuration="PT2H0M" actualTime="{date2}T04:00:00+01:00" duration="PT2H0M" time="{date2}T04:00:00+01:00"/>
                </location>
                <mediaDescription>
                    <longDescription>Be a part of the 4am squad! Lauren Layfield&#39;s here for all the late-night workers and early morning risers | @capitalofficial</longDescription>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="320" url="http://mediaweb.musicradio.com/ArtWork/SES/d356d073-4ada-47cd-9c50-e1c9610b3bea" width="600"/>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="2048" url="http://mediaweb.musicradio.com/ArtWork/SES/6953847b-871f-493d-83d7-2be9cd802276" width="2048"/>
                </mediaDescription>
                <memberOf id="crid://epg.musicradio.com/programmes/7838" shortId="7838"/>
                <link uri="http://www.capitalfm.com/radio/shows-presenters/"/>
            </programme>
            <programme id="crid://epg.musicradio.com/programmes/6825/1706861" shortId="1706861">
                <shortName>Roman</shortName>
                <mediumName>Roman Kemp</mediumName>
                <longName>Capital Breakfast with Roman Kemp</longName>
                <location>
                    <time actualDuration="PT4H0M" actualTime="{date2}T06:00:00+01:00" duration="PT4H0M" time="{date2}T06:00:00+01:00"/>
                </location>
                <mediaDescription>
                    <longDescription>Roman Kemp, Sian Welby and Sonny Jay are joined by the biggest guests as they wake up the UK | @capitalofficial</longDescription>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="320" url="http://mediaweb.musicradio.com/ArtWork/SES/c1cdc852-f1c3-497d-aa86-13bdfedc253e" width="600"/>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="2048" url="http://mediaweb.musicradio.com/ArtWork/SES/d808b937-d413-4828-8492-2abe0cf3e653" width="2048"/>
                </mediaDescription>
                <memberOf id="crid://epg.musicradio.com/programmes/6825" shortId="6825"/>
                <link uri="http://www.capitalfm.com/roman/"/>
            </programme>
            <programme id="crid://epg.musicradio.com/programmes/6039/1706862" shortId="1706862">
                <shortName>Will</shortName>
                <mediumName>Will Manning</mediumName>
                <longName>Will Manning</longName>
                <location>
                    <time actualDuration="PT3H0M" actualTime="{date2}T10:00:00+01:00" duration="PT3H0M" time="{date2}T10:00:00+01:00"/>
                </location>
                <mediaDescription>
                    <longDescription>It&#39;s on. Will Manning&#39;s bringing all the sass to your weekday morning | @capitalofficial</longDescription>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="320" url="http://mediaweb.musicradio.com/ArtWork/SES/17155de6-847f-46f8-a785-9ff928e5a2db" width="600"/>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="2048" url="http://mediaweb.musicradio.com/ArtWork/SES/8ecd2a15-8d99-4d2d-b56d-b72137374653" width="2048"/>
                </mediaDescription>
                <memberOf id="crid://epg.musicradio.com/programmes/6039" shortId="6039"/>
                <link uri="http://www.capitalfm.com/radio/shows-presenters/will-manning"/>
            </programme>
            <programme id="crid://epg.musicradio.com/programmes/5428/1706863" shortId="1706863">
                <shortName>Aimee</shortName>
                <mediumName>Aimee Vivian</mediumName>
                <longName>Aimee Vivian</longName>
                <location>
                    <time actualDuration="PT3H0M" actualTime="{date2}T13:00:00+01:00" duration="PT3H0M" time="{date2}T13:00:00+01:00"/>
                </location>
                <mediaDescription>
                    <longDescription>Oh hey, babes. Aimee Vivian&#39;s here with all the latest goss this afternoon | @capitalofficial</longDescription>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="320" url="http://mediaweb.musicradio.com/ArtWork/SES/80c200b8-e5b4-481e-b9d1-a8f371577c99" width="600"/>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="2048" url="http://mediaweb.musicradio.com/ArtWork/SES/35b4c528-b6a0-4e7e-9dc2-d2548f7214f4" width="2048"/>
                </mediaDescription>
                <memberOf id="crid://epg.musicradio.com/programmes/5428" shortId="5428"/>
                <link uri="http://www.capitalfm.com/radio/shows-presenters/aimee-vivian"/>
            </programme>
            <programme id="crid://epg.musicradio.com/programmes/2250/1717360" shortId="1717360">
                <shortName>Rob</shortName>
                <mediumName>Rob Howard</mediumName>
                <longName>Rob Howard</longName>
                <location>
                    <time actualDuration="PT3H0M" actualTime="{date2}T16:00:00+01:00" duration="PT3H0M" time="{date2}T16:00:00+01:00"/>
                </location>
                <mediaDescription>
                    <longDescription>Rob Howard&#39;s in for Ant Payne this week, getting you home with some nonsense and a few tunes | @capitalofficial</longDescription>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="320" url="http://mediaweb.musicradio.com/ArtWork/SES/d8ae0190-7b79-4e8c-a6bd-8794732d82f4" width="600"/>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="2048" url="http://mediaweb.musicradio.com/ArtWork/SES/9798d60b-fae6-4195-bdfe-d22b403dd923" width="2048"/>
                </mediaDescription>
                <memberOf id="crid://epg.musicradio.com/programmes/2250" shortId="2250"/>
                <link uri="http://www.capitalfm.com/radio/shows-presenters/rob-howard"/>
            </programme>
            <programme id="crid://epg.musicradio.com/programmes/6919/1706864" shortId="1706864">
                <shortName>Jimmy</shortName>
                <mediumName>Jimmy Hill</mediumName>
                <longName>The Capital Evening Show With Jimmy Hill</longName>
                <location>
                    <time actualDuration="PT3H0M" actualTime="{date2}T19:00:00+01:00" duration="PT3H0M" time="{date2}T19:00:00+01:00"/>
                </location>
                <mediaDescription>
                    <longDescription>Jimmy Hill is FaceTiming the biggest stars in their bedrooms on The Capital Evening Show | @capitalofficial</longDescription>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="320" url="http://mediaweb.musicradio.com/ArtWork/SES/8cb0a401-118d-4f9b-8fcb-e698053ab4e2" width="600"/>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="2048" url="http://mediaweb.musicradio.com/ArtWork/SES/d45201be-c50e-42bb-b461-c9e879df143e" width="2048"/>
                </mediaDescription>
                <memberOf id="crid://epg.musicradio.com/programmes/6919" shortId="6919"/>
                <link uri="http://www.capitalfm.com/radio/shows-presenters/jimmy-hill/"/>
            </programme>
            <programme id="crid://epg.musicradio.com/programmes/2817/1706865" shortId="1706865">
                <shortName>Marvin</shortName>
                <mediumName>Marvin Humes</mediumName>
                <longName>The Capital Late Show With Marvin Humes</longName>
                <location>
                    <time actualDuration="PT3H0M" actualTime="{date2}T22:00:00+01:00" duration="PT3H0M" time="{date2}T22:00:00+01:00"/>
                </location>
                <mediaDescription>
                    <longDescription>Marvin Humes has your late-night soundtrack sorted with the best new music around | @capitalofficial</longDescription>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="320" url="http://mediaweb.musicradio.com/ArtWork/SES/e0366f1b-0f34-4b35-a70d-db10567199ca" width="600"/>
                </mediaDescription>
                <mediaDescription>
                    <multimedia height="2048" url="http://mediaweb.musicradio.com/ArtWork/SES/99221a8c-7eed-446e-9f26-eae75fbd41f5" width="2048"/>
                </mediaDescription>
                <memberOf id="crid://epg.musicradio.com/programmes/2817" shortId="2817"/>
                <link uri="http://www.capitalfm.com/radio/shows-presenters/marvin-humes"/>
            </programme>
    </schedule>
</epg>"""
                            programme_info = xml.unmarshall(data)
                            schedule_info = programme_info.schedules[0]

                            # get the right scope - not entirely clear from the specification how multiple schedules should be handled
                            # in terms of scope signalling
                            scope_start = schedule_info.scope.start
                            scope_end = schedule_info.scope.end
                            try:
                                for schedule in programme_info.schedules:
                                    start = schedule.scope.start
                                    end = schedule.scope.end
                                    if scope_start == None or start < scope_start: scope_start = start
                                    if scope_end == None or end > scope_end: scope_start = start
                                    start, end = calculate_scope(schedule)
                                    if scope_start == None or start < scope_start: scope_start = start
                                    if scope_end == None or end > scope_end: scope_end = end

                                    # if there are no SHORT descriptions, form them from suitable length LONG descriptions
                                    for programme in schedule.programmes:
                                        if len(list(filter(lambda x : isinstance(x, ShortDescription), programme.descriptions))) == 0:
                                            long_descriptions = list(filter(lambda x : isinstance(x, LongDescription) and len(x.text) <= 180, programme.descriptions))
                                            for long_description in long_descriptions:
                                                programme.descriptions.append(ShortDescription(long_description.text))


                                    # here we force the schedule/scope/serviceScope to be the current bearer, regardless
                                    schedule_info.scope.bearers = []
                                    schedule_info.scope.bearers.append(bearer)

                                    # and update the start/end times to reflect the schedule times
                                    schedule_info.scope.start = scope_start
                                    schedule_info.scope.end = scope_end
                                    programme_info.schedules[0] = schedule_info

                                    o = MotObject(filename, binary.marshall(programme_info).tobytes(), EpgContentType.PROGRAMME_INFORMATION)
                                    o.add_parameter(ScopeId(bearer.ecc, bearer.eid, sid=bearer.sid, scids=bearer.scids)) 
                                    o.add_parameter(ScopeStart(scope_start))
                                    o.add_parameter(ScopeEnd(scope_end))
                                    objects.append(o)

                            except AttributeError:
                                logger.exception('PI file at: %s is malformed', pi_url)

                     
        # prepare the SI file
        filename = '%s_%02x.%04x_SI.xml' % (now.strftime("%Y%m%d"),ecc,eid)
        # Uncomment this next line if you want to use legacy filenames
        # filename = 'e%02x%04xw%s.EIA' % (ecc, eid, now.strftime("%Y%m%d"))
        service_info = ServiceInfo()
        service_info.originator = "OpenDigitalRadio SPI Generator"
        logger.debug('Preparing the Ensemble SI file with %d services to write out as %s', len(all_services), filename)
        for service in all_services:
    
            # filter out non DAB/IP bearers
            # service.bearers = filter(lambda x: isinstance(x, (DabBearer, IpBearer)), service.bearers)
            # service_info.services.append(service)  

            # filter out non DAB bearers
            # service.bearers = filter(lambda x: isinstance(x, DabBearer), service.bearers)

            # find only the bearer for this multiplex
            service.bearers = filter(lambda x: isinstance(x, DabBearer) and x.ecc == ecc and x.eid == eid, service.bearers)
            service_info.services.append(service)  

        # add the SI file 
        ensemble_label = MediumName(label)
        ensemble_shortlabel = ShortName(shortlabel)
        o = MotObject(filename, binary.marshall(service_info, ensemble=binary.Ensemble(ecc, eid, names=[ensemble_label,ensemble_shortlabel])).tobytes(), EpgContentType.SERVICE_INFORMATION)
        o.add_parameter(ScopeId(ecc, eid))
        objects.append(o)

        logger.debug('loaded into %d MOT objects', len(objects))


        # encode to datagroups
        # datagroups = encode_directorymode(objects, directory_parameters=[SortedHeaderInformation()])
        datagroups = encode_directorymode(objects, directory_parameters=[])
        logger.debug('encoded to %d datagroups', len(datagroups))
        
        # open the output file
        if self.datagroup_output:
            w = open(self.output, 'wb')
       
            for datagroup in datagroups:
                w.write(datagroup.tobytes())

        else:
            packets = encode_packets(datagroups, address=self.packet_address, size=self.packet_size)
            logger.debug('encoded to %d packets', len(packets))

            # open the output file
            w = open(self.output, 'wb')
 
            for packet in packets:
               w.write(packet.tobytes())

# get the services and applications that the mux config suggests and construct a new virtual SI file
from odr.radiodns.resolver import resolve_epg, parse_mux_ensemble
ecc, eid, label, shortlabel = parse_mux_ensemble(args.f[0])
generator = Generator(days=args.days, output=args.output, packet_size=args.packet_size, packet_address=args.packet_address, datagroup_output=args.datagroup_output,ecc=ecc,eid=eid,label=label,shortlabel=shortlabel)
resolved = resolve_epg(args.f[0], generator.generate_epg)
